Benchmarking Geral

  • Objetivo: Avaliar a classificação de séries temporais usando 3 diferentes abordagens, incluindo nossa hipótese, de codificar séries temporais através do Gráfico de Recorrência;

  • Cenário Comparativo:

    • Dados: Considerando os dados estabelecidos no Benchmarking 1;

      • Detalhes: Base REDD, baixa Frequência, Resid. 3/9 aparelhos e 80% dados de treino/20% teste);
      • Amostras: Blocos de 5 minutos (300 segundos - 100 unidade, dado delay 3s) de cada medição;
    • Atributos *(Feature Space)*: representação vetorial das amostras;

      1. Abordagem Estatística (Benchmarking 1): Média, Desvio Padrão, Máximo, Energia Total, Hora do Dia e Temperatura Ambiente (zerado, neste caso, pois não foi disponibilizado pelos autores);
      2. Abordagem GAF (Benchmarking 2): Representação visual da amostra, através do algoritmo Wang and Oates’[20]/Gramian Angular Field Matrices (GAF), e subsequente embedding com uma Rede Neural com arquitetura VGG16;
      3. Abordagem RP (Hipótese 1): Nossa hipótese, converter a amostra em uma representação visual com a técnica de Gráfico de Recorrência (RP, do inglês Recurrence Plot); seguindo o Benchmarking 2, é realizado o embedding da imagem resultante com uma Rede Neural com arquitetura VGG16.
      4. Abordagem RP+RQA (Hipótese 2): A partir do RP, extrair métricas de RQA, as quais alimentarão o classificador de cargas.
    • Método de Classificação: Rede Neural Multi-camadas (MLP, sem hiperparametrização - pacote Scikit-lean);

    • Métrica(s): uma vez que o problema irá ser tratado como classificação multi-rótulo, irão ser adotadas as seguintes métricas (via pacote Scikit-learn.metrics)

      • Classificaton Report, contendo:
        • Acurácia;
        • Precisão;
        • Recall;
        • F1-score;
        • Hamming Loss;
        • Suporte;
      • Matriz de confusão.

Configurando ambiente e parâmetros


In [8]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('ggplot')
plt.rc('text', usetex=False)
from matplotlib.image import imsave
import pandas as pd
import pickle as cPickle
import os, sys, cv2
from math import *
from pprint import pprint
from tqdm import tqdm_notebook
from mpl_toolkits.axes_grid1 import make_axes_locatable
from PIL import Image
from glob import glob
from IPython.display import display

from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.preprocessing import image as keras_image
from tensorflow.keras.applications.vgg16 import preprocess_input

from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_score, precision_score, recall_score, accuracy_score, hamming_loss

from pyts.image import RecurrencePlot

REDD_RESOURCES_PATH = 'datasets/REDD'

BENCHMARKING1_RESOURCES_PATH = 'benchmarkings/cs446 project-electric-load-identification-using-machine-learning/'
BENCHMARKING2_RESOURCES_PATH = 'benchmarkings/Imaging-NILM-time-series/'
HYPOTHESIS_RESOURCES_PATH = 'datasets/hipotese1-recurrenceplot-vggembedding/'

sys.path.append(os.path.join(BENCHMARKING1_RESOURCES_PATH, ''))
sys.path.append(os.path.join(BENCHMARKING2_RESOURCES_PATH, ''))
sys.path.append(os.path.join(HYPOTHESIS_RESOURCES_PATH, ''))

from serie2QMlib import *

import warnings
warnings.filterwarnings(action='ignore')

Carregando os dados


In [9]:
# devices to be used in training and testing
use_idx = np.array([3,4,6,7,10,11,13,17,19])

label_columns_idx = ["APLIANCE_{}".format(i) for i in use_idx]

appliance_labels = [
    "Electronics", 
    "Refrigerator", 
    "Dishwasher",
    "Furnace", 
    "Washer Dryer 1", 
    "Washer Dryer 2", 
    "Microwave", 
    "Bathroom GFI",
    "Kitchen Outlets"
]

Informações Estatísticas (Bench. 1)


In [10]:
Xb1_train = np.load( os.path.join(BENCHMARKING1_RESOURCES_PATH, 'datasets/train_instances.npy') )
yb1_train = np.load( os.path.join(BENCHMARKING1_RESOURCES_PATH, 'datasets/train_labels_binary.npy') )

Xb1_test = np.load( os.path.join(BENCHMARKING1_RESOURCES_PATH, 'datasets/test_instances.npy') )
yb1_test = np.load( os.path.join(BENCHMARKING1_RESOURCES_PATH, 'datasets/test_labels_binary.npy') )

Imagens GAF (Bench. 2)


In [11]:
Xb2_train = np.load( os.path.join(BENCHMARKING2_RESOURCES_PATH, 'datasets/X_train.npy') )
yb2_train = np.load( os.path.join(BENCHMARKING2_RESOURCES_PATH, 'datasets/y_train.npy') )

Xb2_test = np.load( os.path.join(BENCHMARKING2_RESOURCES_PATH, 'datasets/X_test.npy') )
yb2_test = np.load( os.path.join(BENCHMARKING2_RESOURCES_PATH, 'datasets/y_test.npy') )

Gráficos de Recorrência (Hipótese 1)


In [12]:
Xh1_train = np.load( os.path.join(HYPOTHESIS_RESOURCES_PATH, 'X_train.npy') )
yh1_train = np.load( os.path.join(HYPOTHESIS_RESOURCES_PATH, 'y_train.npy') )

Xh1_test = np.load( os.path.join(HYPOTHESIS_RESOURCES_PATH, 'X_test.npy') )
yh1_test = np.load( os.path.join(HYPOTHESIS_RESOURCES_PATH, 'y_test.npy') )

RP+RQA (Hipótese 2)


In [13]:
""" 
References:
    - https://contentlab.io/using-rqa-and-neural-networks-to-analyze-complex-time-series/
    - https://github.com/JuliaDynamics/RecurrenceAnalysis.jl/wiki/Comparison-of-software-packages-for-RQA
    - PyRQA
    - https://stackoverflow.com/questions/43077427/python-pyopencl-import-error
"""

# # Erro: 
#     RuntimeError: clGetDeviceInfo failed: OUT_OF_RESOURCES
# # Solução: 
#     1. Adicionar o comando 'if self.verbose:' em ~\anaconda3\envs\doutorado\lib\site-packages\pyrqa\opencl.py na linha 262
#     2. Implementar try_or function em ~\anaconda3\envs\doutorado\lib\site-packages\pyrqa\opencl.py e alterar linha 510-511 (função get_device_info)
#         def try_or(fn, default):
#             try:
#                 return fn()
#             except:
#                 return default
#    
#         import pyopencl as cl  # Import the OpenCL GPU computing API
#         print('\n' + '=' * 60 + '\nOpenCL Platforms and Devices')
#         for platform in cl.get_platforms():  # Print each platform on this computer
#             print('=' * 60)
#             print('Platform - Name:  ' + platform.name)
#             print('Platform - Vendor:  ' + platform.vendor)
#             print('Platform - Version:  ' + platform.version)
#             print('Platform - Profile:  ' + platform.profile)
#             for device in platform.get_devices():  # Print each device per-platform
#                 print('    ' + '-' * 56)
#                 print('    Device - Name:  ' + device.name)
#                 print('    Device - Type:  ' + cl.device_type.to_string(device.type))
#                 #print('    Device - Max Clock Speed:  {0} Mhz'.format(device.max_clock_frequency))
#                 print('    Device - Max Clock Speed:  {0} Mhz'.format( try_or(lambda: device.max_clock_frequency, '<n/a>') ) )
#                 print('    Device - Compute Units:  {0}'.format(device.max_compute_units))
#                 print('    Device - Local Memory:  {0:.0f} KB'.format(device.local_mem_size/1024))
#                 print('    Device - Constant Memory:  {0:.0f} KB'.format(device.max_constant_buffer_size/1024))
#                 print('    Device - Global Memory: {0:.0f} GB'.format(device.global_mem_size/1073741824.0))
#         print('\n')


Out[13]:
' \nReferences:\n    - https://contentlab.io/using-rqa-and-neural-networks-to-analyze-complex-time-series/\n    - https://github.com/JuliaDynamics/RecurrenceAnalysis.jl/wiki/Comparison-of-software-packages-for-RQA\n    - PyRQA\n    - https://stackoverflow.com/questions/43077427/python-pyopencl-import-error\n'

In [18]:
from pyrqa.time_series import TimeSeries
from pyrqa.settings import Settings
from pyrqa.computing_type import ComputingType
from pyrqa.neighbourhood import FixedRadius
from pyrqa.metric import EuclideanMetric
from pyrqa.computation import RQAComputation

def calculate_rqa(series, result_format = 'pandas'):
    rqa_data = []
    for serie in tqdm_notebook(series):
        settings = Settings(
            TimeSeries(
                serie, 
                embedding_dimension=1, time_delay=1 # Same series from PyTS package used in our hypothesis (https://pyts.readthedocs.io/en/latest/generated/pyts.image.RecurrencePlot.html#pyts.image.RecurrencePlot)
            ), # Performing RP conversions from TS
            computing_type=ComputingType.Classic,
            neighbourhood=FixedRadius(0.65),
            similarity_measure=EuclideanMetric,
            theiler_corrector=1
        )
        rqa_result = RQAComputation.create(settings,verbose=False).run()
        rqa_data.append(rqa_result.to_array()[3:])
        
    if result_format == 'pandas':
        return pd.DataFrame(
            data = rqa_data, 
            columns =  [
                "Recurrence rate (RR)",
                "Determinism (DET)", 
                "Average diagonal line length (L)",
                "Longest diagonal line length (L_max)",
                "Divergence (DIV)",
                "Entropy diagonal lines (L_entr)",
                "Laminarity (LAM)",
                "Trapping time (TT)",
                "Longest vertical line length (V_max)",
                "Entropy vertical lines (V_entr)",
                "Average white vertical line length (W)",
                "Longest white vertical line length (W_max)",
                "Longest white vertical line length inverse (W_div)",
                "Entropy white vertical lines (W_entr)",
                "Ratio determinism / recurrence rate (DET/RR)",
                "Ratio laminarity / determinism (LAM/DET)"
            ]
        )
    else:
        return rqa_data
    
# # Testing RQA calc...
# data_points = [
#     [0.1, 0.5, 1.3, 0.7, 0.8, 1.4, 1.6, 1.2, 0.4, 1.1, 0.8, 0.2, 1.3],
#     [1.6, 1.2, 0.4, 1.1, 0.8, 0.2, 1.3, 0.1, 0.5, 1.3, 0.7, 0.8, 1.4]
# ]
# rqa_series = calculate_rqa(data_points)
# rqa_series

In [19]:
print("Calculating RQA from chunked series dataset (train / test)...")

# Train...
Xh2_train = calculate_rqa(
    np.load( os.path.join(BENCHMARKING1_RESOURCES_PATH, 'datasets/train_power_chunks.npy') )
).replace([np.inf, -np.inf], np.nan).fillna(0)
Xh2_train.to_csv( os.path.join(HYPOTHESIS_RESOURCES_PATH, 'df_rqa_train.csv') )

# Test...
Xh2_test = calculate_rqa(
    np.load( os.path.join(BENCHMARKING1_RESOURCES_PATH, 'datasets/test_power_chunks.npy') )
).replace([np.inf, -np.inf], np.nan).fillna(0)
Xh2_test.to_csv( os.path.join(HYPOTHESIS_RESOURCES_PATH, 'df_rqa_test.csv') )


Calculating RQA from chunked series dataset (train / test)...


Avaliando Classificadores


In [62]:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import multilabel_confusion_matrix

# def metrics(test, predicted):
#     return acc, prec, rec, f1, f1m, hl

def plot_predicted_and_ground_truth(test, predicted):
    #import matplotlib.pyplot as plt
    plt.plot(predicted.flatten(), label = 'pred')
    plt.plot(test.flatten(), label= 'Y')
    plt.show();
    return

def classification_report(y_test, y_pred, labels = None):
    
    final_performance = []
    
    for i in range(y_test.shape[1]):
    
        test = y_test[:, i]
        predicted = y_pred[:, i]
        #acc, prec, rec, f1, f1m, hl, supp = metrics(y_test[:, i], y_pred[:, i])
        acc = accuracy_score(test, predicted)
        prec = precision_score(test, predicted)
        rec = recall_score(test, predicted)    
        f1 = f1_score(test, predicted)
        f1m = f1_score(test, predicted, average='macro')
        hl = hamming_loss(test, predicted)   
        supp = y_test.shape[0]

        final_performance.append([
            labels[i] if labels is not None else label_columns_idx[i], 
            round(acc*100, 2), 
            round(prec*100, 2), 
            round(rec*100, 2), 
            round(f1*100, 2), 
            round(f1m*100, 2),
            round(hl, 2),
            supp
        ])

    print("CLASSIFIER PERFORMANCE BY APPLIANCE (LABEL):")
    df_metrics = pd.DataFrame(
        data = final_performance,
        columns = ["Appliance", "Accuracy", "Precision", "Recall", "F1-score", "F1-macro", "Hamming Loss", "Support"]
    )
    display(df_metrics)

    print("")
    print("OVERALL AVERAGE PERFORMANCE:")
    display(df_metrics.describe().round(2).loc[['mean','max','min']])
    
    print("")
    print("CONFUSION MATRIX (OFF/ON), BY APPLIANCE:")
    
    cms = multilabel_confusion_matrix(y_test, y_pred)
    for i, a in enumerate(appliance_labels):
        print("")
        print(" - {}:".format(a))
        print(cms[i])
    #print(, labels= appliance_labels)
    
# Default model classifiers
mlp_classifier = Pipeline([
#     ('scaler', StandardScaler()), 
    ('clf', MLPClassifier( random_state = 33))
])  
svm_classifier = Pipeline([
#     ('scaler', StandardScaler()), 
    ('clf', SVC())
])

MLP

Benchmarking 1


In [63]:
model_b1 = mlp_classifier#RandomForestClassifier(n_estimators=10)#DecisionTreeClassifier(max_depth=15)
model_b1.fit(Xb1_train, yb1_train)

y_test = np.array(yb1_test)
y_pred = np.array(model_b1.predict(Xb1_test))

classification_report(y_test, y_pred, labels = appliance_labels)


CLASSIFIER PERFORMANCE BY APPLIANCE (LABEL):
Appliance Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
0 Electronics 50.48 50.48 100.00 67.09 33.54 0.50 4000
1 Refrigerator 58.50 77.78 0.84 1.66 37.68 0.42 4000
2 Dishwasher 99.18 0.00 0.00 0.00 49.79 0.01 4000
3 Furnace 99.42 0.00 0.00 0.00 49.86 0.01 4000
4 Washer Dryer 1 98.40 60.00 60.00 60.00 79.59 0.02 4000
5 Washer Dryer 2 97.10 57.30 39.53 46.79 72.65 0.03 4000
6 Microwave 99.52 0.00 0.00 0.00 49.88 0.00 4000
7 Bathroom GFI 97.02 18.25 58.97 27.88 63.18 0.03 4000
8 Kitchen Outlets 89.15 0.00 0.00 0.00 47.13 0.11 4000
OVERALL AVERAGE PERFORMANCE:
Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
mean 87.64 29.31 28.82 22.60 53.70 0.13 4000.0
max 99.52 77.78 100.00 67.09 79.59 0.50 4000.0
min 50.48 0.00 0.00 0.00 33.54 0.00 4000.0
CONFUSION MATRIX (OFF/ON), BY APPLIANCE:

 - Electronics:
[[   0 1981]
 [   0 2019]]

 - Refrigerator:
[[2326    4]
 [1656   14]]

 - Dishwasher:
[[3967    0]
 [  33    0]]

 - Furnace:
[[3977    0]
 [  23    0]]

 - Washer Dryer 1:
[[3888   32]
 [  32   48]]

 - Washer Dryer 2:
[[3833   38]
 [  78   51]]

 - Microwave:
[[3981    0]
 [  19    0]]

 - Bathroom GFI:
[[3858  103]
 [  16   23]]

 - Kitchen Outlets:
[[3566    0]
 [ 434    0]]

Benchmarking 2


In [64]:
model_b2 = mlp_classifier#RandomForestClassifier(n_estimators=10)#DecisionTreeClassifier(max_depth=15)
model_b2.fit(Xb2_train, yb2_train)

y_test = np.array(yb2_test)
y_pred = np.array(model_b2.predict(Xb2_test).astype(int))

classification_report(y_test, y_pred, labels = appliance_labels)


CLASSIFIER PERFORMANCE BY APPLIANCE (LABEL):
Appliance Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
0 Electronics 52.75 52.82 59.88 56.13 52.47 0.47 4000
1 Refrigerator 61.65 54.46 49.76 52.00 60.04 0.38 4000
2 Dishwasher 98.48 0.00 0.00 0.00 49.62 0.02 4000
3 Furnace 95.98 2.74 17.39 4.73 51.34 0.04 4000
4 Washer Dryer 1 98.88 78.69 60.00 68.09 83.76 0.01 4000
5 Washer Dryer 2 97.25 61.73 38.76 47.62 73.10 0.03 4000
6 Microwave 99.15 14.29 15.79 15.00 57.29 0.01 4000
7 Bathroom GFI 96.68 4.81 12.82 6.99 52.65 0.03 4000
8 Kitchen Outlets 88.65 31.48 3.92 6.97 50.46 0.11 4000
OVERALL AVERAGE PERFORMANCE:
Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
mean 87.72 33.45 28.7 28.61 58.97 0.12 4000.0
max 99.15 78.69 60.0 68.09 83.76 0.47 4000.0
min 52.75 0.00 0.0 0.00 49.62 0.01 4000.0
CONFUSION MATRIX (OFF/ON), BY APPLIANCE:

 - Electronics:
[[ 901 1080]
 [ 810 1209]]

 - Refrigerator:
[[1635  695]
 [ 839  831]]

 - Dishwasher:
[[3939   28]
 [  33    0]]

 - Furnace:
[[3835  142]
 [  19    4]]

 - Washer Dryer 1:
[[3907   13]
 [  32   48]]

 - Washer Dryer 2:
[[3840   31]
 [  79   50]]

 - Microwave:
[[3963   18]
 [  16    3]]

 - Bathroom GFI:
[[3862   99]
 [  34    5]]

 - Kitchen Outlets:
[[3529   37]
 [ 417   17]]

Hipótese 1: RP+Vgg


In [65]:
model_h1  = mlp_classifier#RandomForestClassifier(n_estimators=10)#DecisionTreeClassifier(max_depth=15)
model_h1.fit(Xh1_train, yh1_train)

y_test = np.array(yh1_test)
y_pred = np.array(model_h1.predict(Xh1_test))

classification_report(y_test, y_pred, labels = appliance_labels)


CLASSIFIER PERFORMANCE BY APPLIANCE (LABEL):
Appliance Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
0 Electronics 56.82 56.52 62.70 59.45 56.64 0.43 4000
1 Refrigerator 62.15 55.76 45.21 49.93 59.75 0.38 4000
2 Dishwasher 98.25 2.56 3.03 2.78 50.95 0.02 4000
3 Furnace 96.40 3.10 17.39 5.26 51.71 0.04 4000
4 Washer Dryer 1 98.82 80.00 55.00 65.19 82.29 0.01 4000
5 Washer Dryer 2 97.15 60.27 34.11 43.56 71.05 0.03 4000
6 Microwave 99.02 14.29 21.05 17.02 58.27 0.01 4000
7 Bathroom GFI 96.92 2.27 5.13 3.15 50.79 0.03 4000
8 Kitchen Outlets 88.88 38.30 4.15 7.48 50.78 0.11 4000
OVERALL AVERAGE PERFORMANCE:
Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
mean 88.27 34.79 27.53 28.20 59.14 0.12 4000.0
max 99.02 80.00 62.70 65.19 82.29 0.43 4000.0
min 56.82 2.27 3.03 2.78 50.78 0.01 4000.0
CONFUSION MATRIX (OFF/ON), BY APPLIANCE:

 - Electronics:
[[1007  974]
 [ 753 1266]]

 - Refrigerator:
[[1731  599]
 [ 915  755]]

 - Dishwasher:
[[3929   38]
 [  32    1]]

 - Furnace:
[[3852  125]
 [  19    4]]

 - Washer Dryer 1:
[[3909   11]
 [  36   44]]

 - Washer Dryer 2:
[[3842   29]
 [  85   44]]

 - Microwave:
[[3957   24]
 [  15    4]]

 - Bathroom GFI:
[[3875   86]
 [  37    2]]

 - Kitchen Outlets:
[[3537   29]
 [ 416   18]]

Hipótese 2: Atributos RQA (RP)


In [66]:
model_h2  = mlp_classifier#RandomForestClassifier(n_estimators=10)#DecisionTreeClassifier(max_depth=15)
model_h2.fit(Xh2_train, yh1_train)

y_test = np.array(yh1_test)
y_pred = np.array(model_h2.predict(Xh2_test))

classification_report(y_test, y_pred, labels = appliance_labels)


CLASSIFIER PERFORMANCE BY APPLIANCE (LABEL):
Appliance Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
0 Electronics 50.80 50.65 98.32 66.86 35.71 0.49 4000
1 Refrigerator 58.15 35.71 0.30 0.59 37.04 0.42 4000
2 Dishwasher 99.18 0.00 0.00 0.00 49.79 0.01 4000
3 Furnace 99.42 0.00 0.00 0.00 49.86 0.01 4000
4 Washer Dryer 1 98.00 0.00 0.00 0.00 49.49 0.02 4000
5 Washer Dryer 2 96.78 0.00 0.00 0.00 49.18 0.03 4000
6 Microwave 99.52 0.00 0.00 0.00 49.88 0.00 4000
7 Bathroom GFI 99.02 0.00 0.00 0.00 49.76 0.01 4000
8 Kitchen Outlets 89.15 0.00 0.00 0.00 47.13 0.11 4000
OVERALL AVERAGE PERFORMANCE:
Accuracy Precision Recall F1-score F1-macro Hamming Loss Support
mean 87.78 9.60 10.96 7.49 46.43 0.12 4000.0
max 99.52 50.65 98.32 66.86 49.88 0.49 4000.0
min 50.80 0.00 0.00 0.00 35.71 0.00 4000.0
CONFUSION MATRIX (OFF/ON), BY APPLIANCE:

 - Electronics:
[[  47 1934]
 [  34 1985]]

 - Refrigerator:
[[2321    9]
 [1665    5]]

 - Dishwasher:
[[3967    0]
 [  33    0]]

 - Furnace:
[[3977    0]
 [  23    0]]

 - Washer Dryer 1:
[[3920    0]
 [  80    0]]

 - Washer Dryer 2:
[[3871    0]
 [ 129    0]]

 - Microwave:
[[3981    0]
 [  19    0]]

 - Bathroom GFI:
[[3961    0]
 [  39    0]]

 - Kitchen Outlets:
[[3566    0]
 [ 434    0]]

SVM

...( Próximo passo, avaliar classificador SVM com suporte a classificação multi-rótulo)...

Conclusões

A utilização de RPs para a classificação multirótulo de cargas, no contexto descrito, demonstra os melhores resultados para as métricas Acurácia, Precisão, F1-score ponderado (macro) e Hamming Loss.


In [ ]: